/*
 * @Description: Be careful what you wish for
 * @Autor: zzzzzed
 * @Date: 2022-12-03 00:02:09
 * @LastEditors: zzzzzed
 * @LastEditTime: 2022-12-03 00:02:10
 */
/**   
 * 设置对象的 setter和getter 劫持对象
 * 
 *  @author IT侠来了
 */

/**
 *
 *  给set方法扩展属性
 * @interface SetStore
 */
interface SetExt<T> {
    /**
     *保存上一次的值
     *
     * @type {T}
     * @memberof SetExt
     */
    value: T;
    /**
     * setter方法
     *
     * @param {T} val
     * @memberof SetExt
     */
    (val: T): void;
    /**
     *
     * 保存所有订阅方法
     * @type {bindCallBack<T>[]}
     * @memberof SetExt
     */
    store: bindCallBack<T>[];
}

type bindCallBack<T> = { (val: T, old: T): void };

/**
 *
 * 创建 一个带属性的setter方法
 * @template T
 * @return {*}  {SetExt<T>}
 */
let createSet = function <T>(): SetExt<T> {
    let set = <SetExt<T>>function (val: T) {
        let oldVal = set!.value;
        set!.value = val;
        set.store!.forEach(f => {
            f(val, oldVal);
        });
    }
    return set;
}

/** 
 * 用于给对象的属性增加 setter 和 getter 已达到数据劫持的目的 
 * 
 * @example
 *  let  obj = {a:"test"}
 * 
 *  bind(obj,"a",function(val ,old){
 *   ....
 *  })
 * 
 * @param obj   需要进行属性数据劫持的对象
 * @param key   对象上的属性
 * @param func  数据有变化调用的回调函数
 * 
 */
export let bind = function <T, K extends keyof T>(obj: T, key: K, func: bindCallBack<T[K]>) {

    let descriptor = Object.getOwnPropertyDescriptor(obj, key);
    let store: bindCallBack<T[K]>[];
    if (descriptor && descriptor.set) {
        (<SetExt<T[K]>>descriptor.set).store.push(func);
        cc.warn("出现了多次绑定，需要检查多次绑定是否是业务需求");
    } else {

        store = [];
        let set = createSet<T[K]>();
        set.store = store;
        store.push(func);
        set.value = obj[key];
        Object.defineProperty(obj, key, { set, get: function () { return set.value } });

    }

}


/** 
 * 解除已经使用了bind的对象 ,把setter ,getter 删除
 * 
 * @example
 *  let  obj = {a:"test"}
 * 
 *  unbind(obj,"a")
 * 
 * @param obj   需要进行属性数据劫持的对象
 * @param key   对象上的属性
 * @param func  数据有变化调用的回调函数
 * 
 */

export let unbind = function <T, K extends keyof T>(obj: T, key: K) {

    let descriptor = Object.getOwnPropertyDescriptor(obj, key);

    if ((<SetExt<T[K]>>descriptor?.set)?.store) {
        (<SetExt<T[K]>>descriptor?.set).store = []
    }
    descriptor.set = null;
    descriptor.get = null;
}
